"""Auth service"""
import logging
from typing import List, Optional

from sqlalchemy.orm import Session

from dbgpt.component import SystemApp
from dbgpt.storage.metadata import BaseDao, db
from dbgpt.util.pagination_utils import PaginationResult
from dbgpt_serve.core import BaseService

from ..api.schemas import (
    LoginRequest,
    LoginResponse,
    MenuRequest,
    MenuResponse,
    RoleRequest,
    RoleResponse,
    TokenResponse,
    UserRequest,
    UserResponse,
)
from ..config import SERVE_SERVICE_COMPONENT_NAME, ServeConfig
from ..models.models import (
    MenuDao,
    MenuEntity,
    RoleDao,
    RoleEntity,
    RoleMenuEntity,
    RoleAppEntity,
    UserDao,
    UserEntity,
    UserRoleEntity,
)
from ..utils import JWTUtils, PasswordUtils

logger = logging.getLogger(__name__)


class Service(BaseService[UserEntity, UserRequest, UserResponse]):
    """Auth service"""

    name = SERVE_SERVICE_COMPONENT_NAME

    def __init__(
        self,
        system_app: SystemApp,
        config: ServeConfig,
        dao: Optional[UserDao] = None,
    ):
        self._system_app = None
        self._serve_config: ServeConfig = config
        self._dao: UserDao = dao
        self._role_dao: Optional[RoleDao] = None
        self._menu_dao: Optional[MenuDao] = None
        super().__init__(system_app)

    def init_app(self, system_app: SystemApp) -> None:
        """Initialize service"""
        super().init_app(system_app)
        self._dao = self._dao or UserDao(self._serve_config)
        self._role_dao = RoleDao(self._serve_config)
        self._menu_dao = MenuDao(self._serve_config)
        self._system_app = system_app

    @property
    def dao(self) -> BaseDao[UserEntity, UserRequest, UserResponse]:
        """Get DAO"""
        return self._dao

    @property
    def config(self) -> ServeConfig:
        """Get config"""
        return self._serve_config

    # ========================================
    # User Management
    # ========================================
    def create_user(self, request: UserRequest) -> UserResponse:
        """Create user"""
        logger.info(f"Creating user: {request.username}, role_ids: {request.role_ids}")
        with db.session() as session:
            # Check username exists
            existing = self._dao.get_by_username(session, request.username)
            if existing:
                raise ValueError(f"Username '{request.username}' already exists")

            # Hash password
            if request.password:
                request.password = PasswordUtils.hash_password(
                    request.password, self._serve_config.password_hash_rounds
                )

            # Create user
            entity = self._dao.from_request(request)
            entity.status = 1
            entity.is_deleted = 0
            session.add(entity)
            session.flush()
            logger.info(f"User created with ID: {entity.id}")

            # Assign roles
            if request.role_ids:
                logger.info(f"Assigning {len(request.role_ids)} roles to user {entity.id}")
                for role_id in request.role_ids:
                    user_role = UserRoleEntity(user_id=entity.id, role_id=role_id)
                    session.add(user_role)
                    logger.info(f"Added role {role_id} to user {entity.id}")

            session.commit()
            
            # Load user with roles for response
            user_response = self._dao.to_response(entity)
            if request.role_ids:
                roles = (
                    session.query(RoleEntity)
                    .filter(RoleEntity.id.in_(request.role_ids), RoleEntity.is_deleted == 0)
                    .all()
                )
                user_response.roles = [self._role_dao.to_response(role) for role in roles]
            
            return user_response

    def update_user(self, user_id: int, request: UserRequest) -> UserResponse:
        """Update user"""
        with db.session() as session:
            entity = session.query(UserEntity).filter(UserEntity.id == user_id).first()
            if not entity:
                raise ValueError(f"User with id {user_id} not found")

            # Update fields
            if request.nick_name is not None:
                entity.nick_name = request.nick_name
            if request.email is not None:
                entity.email = request.email
            if request.phone is not None:
                entity.phone = request.phone
            if request.avatar_url is not None:
                entity.avatar_url = request.avatar_url
            if request.status is not None:
                entity.status = request.status
            if request.remark is not None:
                entity.remark = request.remark

            # Update password if provided
            if request.password:
                entity.password = PasswordUtils.hash_password(
                    request.password, self._serve_config.password_hash_rounds
                )

            # Update roles
            if request.role_ids is not None:
                # Delete old roles
                session.query(UserRoleEntity).filter(
                    UserRoleEntity.user_id == user_id
                ).delete()
                # Add new roles
                for role_id in request.role_ids:
                    user_role = UserRoleEntity(user_id=user_id, role_id=role_id)
                    session.add(user_role)

            session.commit()
            
            # Load user with roles for response
            user_response = self._dao.to_response(entity)
            role_ids = self._dao.get_user_roles(session, user_id)
            if role_ids:
                roles = (
                    session.query(RoleEntity)
                    .filter(RoleEntity.id.in_(role_ids), RoleEntity.is_deleted == 0)
                    .all()
                )
                user_response.roles = [self._role_dao.to_response(role) for role in roles]
            
            return user_response

    def delete_user(self, user_id: int) -> bool:
        """Delete user (soft delete)"""
        with db.session() as session:
            entity = session.query(UserEntity).filter(UserEntity.id == user_id).first()
            if not entity:
                return False

            entity.is_deleted = 1
            session.commit()
            return True

    def get_user_by_id(self, user_id: int) -> Optional[UserResponse]:
        """Get user by ID"""
        with db.session() as session:
            entity = (
                session.query(UserEntity)
                .filter(UserEntity.id == user_id, UserEntity.is_deleted == 0)
                .first()
            )
            if not entity:
                return None
            return self._dao.to_response(entity)

    def get_user_by_username(self, username: str) -> Optional[UserEntity]:
        """Get user entity by username"""
        with db.session() as session:
            return self._dao.get_by_username(session, username)

    def list_users(
        self, page: int = 1, page_size: int = 20
    ) -> PaginationResult[UserResponse]:
        """List users"""
        with db.session() as session:
            query = session.query(UserEntity).filter(UserEntity.is_deleted == 0)
            total = query.count()
            entities = (
                query.order_by(UserEntity.id.desc())
                .offset((page - 1) * page_size)
                .limit(page_size)
                .all()
            )

            items = []
            for entity in entities:
                user_response = self._dao.to_response(entity)
                # 加载用户的角色
                role_ids = self._dao.get_user_roles(session, entity.id)
                if role_ids:
                    roles = (
                        session.query(RoleEntity)
                        .filter(RoleEntity.id.in_(role_ids), RoleEntity.is_deleted == 0)
                        .all()
                    )
                    user_response.roles = [self._role_dao.to_response(role) for role in roles]
                items.append(user_response)
            
            return PaginationResult(
                items=items,
                total_count=total,
                total_pages=(total + page_size - 1) // page_size,
                page=page,
                page_size=page_size,
            )

    # ========================================
    # Role Management
    # ========================================
    def create_role(self, request: RoleRequest) -> RoleResponse:
        """Create role"""
        with db.session() as session:
            # Check role code exists
            existing = (
                session.query(RoleEntity)
                .filter(
                    RoleEntity.role_code == request.role_code,
                    RoleEntity.is_deleted == 0,
                )
                .first()
            )
            if existing:
                raise ValueError(f"Role code '{request.role_code}' already exists")

            # Create role
            entity = self._role_dao.from_request(request)
            entity.status = 1
            entity.is_deleted = 0
            session.add(entity)
            session.flush()

            # Assign menus
            if request.menu_ids:
                for menu_id in request.menu_ids:
                    role_menu = RoleMenuEntity(role_id=entity.id, menu_id=menu_id)
                    session.add(role_menu)

            # Assign apps
            if request.app_codes:
                for app_code in request.app_codes:
                    role_app = RoleAppEntity(role_id=entity.id, app_code=app_code)
                    session.add(role_app)

            session.commit()
            return self._role_dao.to_response(entity)

    def update_role(self, role_id: int, request: RoleRequest) -> RoleResponse:
        """Update role"""
        with db.session() as session:
            entity = session.query(RoleEntity).filter(RoleEntity.id == role_id).first()
            if not entity:
                raise ValueError(f"Role with id {role_id} not found")

            # Update fields
            if request.role_name is not None:
                entity.role_name = request.role_name
            if request.role_desc is not None:
                entity.role_desc = request.role_desc
            if request.status is not None:
                entity.status = request.status
            if request.remark is not None:
                entity.remark = request.remark

            # Update menus
            if request.menu_ids is not None:
                # Delete old menus
                session.query(RoleMenuEntity).filter(
                    RoleMenuEntity.role_id == role_id
                ).delete()
                # Add new menus
                for menu_id in request.menu_ids:
                    role_menu = RoleMenuEntity(role_id=role_id, menu_id=menu_id)
                    session.add(role_menu)

            # Update apps
            if request.app_codes is not None:
                # Delete old apps
                session.query(RoleAppEntity).filter(
                    RoleAppEntity.role_id == role_id
                ).delete()
                # Add new apps
                for app_code in request.app_codes:
                    role_app = RoleAppEntity(role_id=role_id, app_code=app_code)
                    session.add(role_app)

            session.commit()
            return self._role_dao.to_response(entity)

    def delete_role(self, role_id: int) -> bool:
        """Delete role (soft delete)"""
        with db.session() as session:
            entity = session.query(RoleEntity).filter(RoleEntity.id == role_id).first()
            if not entity:
                return False

            entity.is_deleted = 1
            session.commit()
            return True

    def list_roles(
        self, page: int = 1, page_size: int = 20
    ) -> PaginationResult[RoleResponse]:
        """List roles"""
        with db.session() as session:
            query = session.query(RoleEntity).filter(RoleEntity.is_deleted == 0)
            total = query.count()
            entities = (
                query.order_by(RoleEntity.id.desc())
                .offset((page - 1) * page_size)
                .limit(page_size)
                .all()
            )

            items = []
            for entity in entities:
                role_response = self._role_dao.to_response(entity)
                # 加载角色的菜单
                menu_ids = self._role_dao.get_role_menus(session, entity.id)
                if menu_ids:
                    menus = (
                        session.query(MenuEntity)
                        .filter(MenuEntity.id.in_(menu_ids), MenuEntity.is_deleted == 0)
                        .all()
                    )
                    role_response.menus = [self._menu_dao.to_response(menu) for menu in menus]
                # 加载角色的应用权限
                app_codes = self._role_dao.get_role_apps(session, entity.id)
                if app_codes:
                    role_response.app_codes = app_codes
                items.append(role_response)
            
            return PaginationResult(
                items=items,
                total_count=total,
                total_pages=(total + page_size - 1) // page_size,
                page=page,
                page_size=page_size,
            )

    # ========================================
    # Menu Management
    # ========================================
    def create_menu(self, request: MenuRequest) -> MenuResponse:
        """Create menu"""
        with db.session() as session:
            # Check menu code exists
            existing = (
                session.query(MenuEntity)
                .filter(
                    MenuEntity.menu_code == request.menu_code,
                    MenuEntity.is_deleted == 0,
                )
                .first()
            )
            if existing:
                raise ValueError(f"Menu code '{request.menu_code}' already exists")

            # Create menu
            entity = self._menu_dao.from_request(request)
            entity.status = 1
            entity.is_deleted = 0
            session.add(entity)
            session.commit()
            return self._menu_dao.to_response(entity)

    def update_menu(self, menu_id: int, request: MenuRequest) -> MenuResponse:
        """Update menu"""
        with db.session() as session:
            entity = session.query(MenuEntity).filter(MenuEntity.id == menu_id).first()
            if not entity:
                raise ValueError(f"Menu with id {menu_id} not found")

            # Update fields
            for field in [
                "parent_id",
                "menu_name",
                "menu_name_en",
                "menu_type",
                "route_path",
                "component_path",
                "icon",
                "sort_order",
                "permission_code",
                "status",
                "is_visible",
                "remark",
            ]:
                value = getattr(request, field, None)
                if value is not None:
                    setattr(entity, field, value)

            session.commit()
            return self._menu_dao.to_response(entity)

    def delete_menu(self, menu_id: int) -> bool:
        """Delete menu (soft delete)"""
        with db.session() as session:
            entity = session.query(MenuEntity).filter(MenuEntity.id == menu_id).first()
            if not entity:
                return False

            entity.is_deleted = 1
            session.commit()
            return True

    def get_menu_tree(self) -> List[MenuResponse]:
        """Get menu tree"""
        with db.session() as session:
            all_menus = (
                session.query(MenuEntity)
                .filter(MenuEntity.is_deleted == 0)
                .order_by(MenuEntity.sort_order.asc())
                .all()
            )

            # Build menu tree
            menu_dict = {menu.id: self._menu_dao.to_response(menu) for menu in all_menus}
            root_menus = []

            for menu_id, menu_resp in menu_dict.items():
                if menu_resp.parent_id == 0:
                    root_menus.append(menu_resp)
                else:
                    parent = menu_dict.get(menu_resp.parent_id)
                    if parent:
                        if parent.children is None:
                            parent.children = []
                        parent.children.append(menu_resp)

            return root_menus

    def get_user_menus(self, user_id: int) -> List[MenuResponse]:
        """Get user menus"""
        with db.session() as session:
            # Get user roles
            role_ids = self._dao.get_user_roles(session, user_id)
            if not role_ids:
                return []

            # Get role menus
            menu_ids_query = (
                session.query(RoleMenuEntity.menu_id)
                .filter(RoleMenuEntity.role_id.in_(role_ids))
                .distinct()
            )
            menu_ids = [row[0] for row in menu_ids_query.all()]

            if not menu_ids:
                return []

            # Get menus
            menus = (
                session.query(MenuEntity)
                .filter(
                    MenuEntity.id.in_(menu_ids),
                    MenuEntity.is_deleted == 0,
                    MenuEntity.status == 1,
                )
                .order_by(MenuEntity.sort_order.asc())
                .all()
            )

            # Build menu tree
            menu_dict = {menu.id: self._menu_dao.to_response(menu) for menu in menus}
            root_menus = []

            for menu_id, menu_resp in menu_dict.items():
                if menu_resp.parent_id == 0:
                    root_menus.append(menu_resp)
                else:
                    parent = menu_dict.get(menu_resp.parent_id)
                    if parent:
                        if parent.children is None:
                            parent.children = []
                        parent.children.append(menu_resp)

            return root_menus

    # ========================================
    # Authentication
    # ========================================
    def login(self, request: LoginRequest) -> LoginResponse:
        """User login"""
        logger.info(f"Login attempt for username: {request.username}")
        
        with db.session() as session:
            # Get user
            user_entity = self._dao.get_by_username(session, request.username)
            if not user_entity:
                logger.warning(f"User not found: {request.username}")
                raise ValueError("Invalid username or password")
            
            logger.info(f"User found: ID={user_entity.id}, Status={user_entity.status}")
            logger.debug(f"Password hash from DB: {user_entity.password[:30]}...")
            logger.debug(f"Input password: {request.password}")

            # Verify password
            password_valid = PasswordUtils.verify_password(request.password, user_entity.password)
            logger.info(f"Password verification result: {password_valid}")
            
            if not password_valid:
                logger.warning(f"Invalid password for user: {request.username}")
                raise ValueError("Invalid username or password")

            # Check user status
            if user_entity.status != 1:
                logger.warning(f"User is disabled: {request.username}")
                raise ValueError("User is disabled")

            # Extract user data before session closes
            user_id = user_entity.id
            username = user_entity.username
            user_response = self._dao.to_response(user_entity)
            
            # Get user roles for menus and permissions
            role_ids = self._dao.get_user_roles(session, user_id)
            
            # Get user menus
            menus = self._get_user_menus_in_session(session, role_ids)
            
            # Get user permissions
            permissions = self._get_user_permissions_in_session(session, role_ids)
        
        # Generate token (outside session)
        token = JWTUtils.create_token(user_id, username, self._serve_config)
        
        logger.info(f"Login successful for user: {request.username}")

        return LoginResponse(
            user=user_response,
            token=TokenResponse(
                access_token=token,
                token_type="Bearer",
                expires_in=JWTUtils.get_token_expire_seconds(self._serve_config),
            ),
            menus=menus,
            permissions=permissions,
        )

    def verify_token(self, token: str) -> Optional[dict]:
        """Verify JWT token"""
        return JWTUtils.decode_token(token, self._serve_config)

    def _get_user_menus_in_session(self, session: Session, role_ids: List[int]) -> List[MenuResponse]:
        """Get user menus within a session"""
        if not role_ids:
            return []
        
        # Get role menus
        menu_ids_query = (
            session.query(RoleMenuEntity.menu_id)
            .filter(RoleMenuEntity.role_id.in_(role_ids))
            .distinct()
        )
        menu_ids = [row[0] for row in menu_ids_query.all()]
        
        if not menu_ids:
            return []
        
        # Get menus
        menus = (
            session.query(MenuEntity)
            .filter(
                MenuEntity.id.in_(menu_ids),
                MenuEntity.is_deleted == 0,
                MenuEntity.status == 1,
            )
            .order_by(MenuEntity.sort_order.asc())
            .all()
        )
        
        # Convert to response and build tree
        menu_dict = {menu.id: self._menu_dao.to_response(menu) for menu in menus}
        root_menus = []
        
        for menu_id, menu_resp in menu_dict.items():
            if menu_resp.parent_id == 0:
                root_menus.append(menu_resp)
            else:
                parent = menu_dict.get(menu_resp.parent_id)
                if parent:
                    if parent.children is None:
                        parent.children = []
                    parent.children.append(menu_resp)
        
        return root_menus

    def _get_user_permissions_in_session(self, session: Session, role_ids: List[int]) -> List[str]:
        """Get user permissions within a session"""
        if not role_ids:
            return []
        
        # Get role menus
        menu_ids_query = (
            session.query(RoleMenuEntity.menu_id)
            .filter(RoleMenuEntity.role_id.in_(role_ids))
            .distinct()
        )
        menu_ids = [row[0] for row in menu_ids_query.all()]
        
        if not menu_ids:
            return []
        
        # Get permission codes
        permissions_query = (
            session.query(MenuEntity.permission_code)
            .filter(
                MenuEntity.id.in_(menu_ids),
                MenuEntity.permission_code.isnot(None),
                MenuEntity.is_deleted == 0,
                MenuEntity.status == 1,
            )
            .distinct()
        )
        permissions = [row[0] for row in permissions_query.all()]
        
        return permissions

    def _get_user_permissions(self, user_id: int) -> List[str]:
        """Get user permissions"""
        with db.session() as session:
            # Get user roles
            role_ids = self._dao.get_user_roles(session, user_id)
            if not role_ids:
                return []

            # Get role menus
            menu_ids_query = (
                session.query(RoleMenuEntity.menu_id)
                .filter(RoleMenuEntity.role_id.in_(role_ids))
                .distinct()
            )
            menu_ids = [row[0] for row in menu_ids_query.all()]

            if not menu_ids:
                return []

            # Get permission codes
            permissions_query = (
                session.query(MenuEntity.permission_code)
                .filter(
                    MenuEntity.id.in_(menu_ids),
                    MenuEntity.permission_code.isnot(None),
                    MenuEntity.is_deleted == 0,
                    MenuEntity.status == 1,
                )
                .distinct()
            )
            permissions = [row[0] for row in permissions_query.all()]

            return permissions
